🎲 Bayesian Optimization
📅 3 января 2026
🔷 1. Суть
- Для дорогих функций: когда каждая оценка дорогая
- Суррогатная модель: Gaussian Process (GP) для аппроксимации
- Acquisition function: баланс exploration/exploitation
- Итеративный процесс: выбор следующей точки для оценки
Макс: f(x), где оценка f(x) дорогая
🔷 2. Базовый код (scikit-optimize)
from skopt import gp_minimize
from skopt.space import Real, Integer
# Определить пространство поиска
space = [
Real(1e-6, 1e-1, prior='log-uniform', name='learning_rate'),
Integer(10, 100, name='n_estimators'),
Integer(3, 10, name='max_depth')
]
# Целевая функция (минимизируем, например, -accuracy)
def objective(params):
lr, n_est, depth = params
model = RandomForestClassifier(
n_estimators=n_est,
max_depth=depth,
random_state=42
)
model.fit(X_train, y_train)
score = model.score(X_val, y_val)
return -score # минимизируем, поэтому отрицательный
# Bayesian Optimization
result = gp_minimize(
objective,
space,
n_calls=50, # число итераций
random_state=42,
verbose=True
)
print(f"Best params: {result.x}")
print(f"Best score: {-result.fun}")
🔷 3. Базовый код (Optuna)
import optuna
def objective(trial):
# Определяем гиперпараметры
lr = trial.suggest_loguniform('learning_rate', 1e-6, 1e-1)
n_est = trial.suggest_int('n_estimators', 10, 100)
depth = trial.suggest_int('max_depth', 3, 10)
# Обучаем модель
model = RandomForestClassifier(
n_estimators=n_est,
max_depth=depth,
random_state=42
)
model.fit(X_train, y_train)
# Возвращаем метрику (максимизируем)
return model.score(X_val, y_val)
# Создать study
study = optuna.create_study(
direction='maximize',
sampler=optuna.samplers.TPESampler()
)
# Оптимизация
study.optimize(objective, n_trials=50)
print(f"Best params: {study.best_params}")
print(f"Best score: {study.best_value}")
🔷 4. Как работает
- Инициализация: несколько случайных точек
- Surrogate Model: обучить GP на текущих точках
- Acquisition Function: найти следующую точку
- Оценка: вычислить f(x) в новой точке
- Повторить: обновить GP и повторить
GP (Gaussian Process): дает предсказание + uncertainty
Acquisition Function: использует оба для выбора
🔷 5. Acquisition Functions
| Функция | Описание | Когда использовать |
|---|
| EI (Expected Improvement) | Ожидаемое улучшение | По умолчанию, баланс |
| PI (Probability of Improvement) | Вероятность улучшения | Консервативный |
| UCB (Upper Confidence Bound) | Верхняя граница доверия | Больше exploration |
| LCB (Lower Confidence Bound) | Нижняя граница | Минимизация |
🔷 6. Параметры оптимизации
| Параметр | Описание | Совет |
|---|
n_calls | Число итераций | 50-200, зависит от бюджета |
n_initial_points | Случайных точек вначале | 10-20 |
acq_func | Acquisition function | 'EI', 'PI', 'gp_hedge' |
kappa | Exploration для UCB | 1.96 по умолчанию |
xi | Exploration для EI/PI | 0.01 по умолчанию |
🔷 7. Типы пространств поиска
from skopt.space import Real, Integer, Categorical
space = [
# Непрерывные параметры
Real(0.001, 0.1, prior='log-uniform', name='lr'),
Real(0.1, 0.9, name='dropout'),
# Целочисленные параметры
Integer(10, 200, name='n_estimators'),
Integer(2, 10, name='max_depth'),
# Категориальные параметры
Categorical(['adam', 'sgd', 'rmsprop'], name='optimizer'),
Categorical([32, 64, 128, 256], name='batch_size')
]
🔷 8. Продвинутый пример с CV
from skopt import gp_minimize
from sklearn.model_selection import cross_val_score
def objective(params):
lr, n_est, depth = params
model = RandomForestClassifier(
n_estimators=n_est,
max_depth=depth,
random_state=42
)
# Кросс-валидация
scores = cross_val_score(
model, X_train, y_train,
cv=5, scoring='accuracy'
)
# Минимизируем отрицательный score
return -scores.mean()
result = gp_minimize(
objective,
space,
n_calls=50,
n_initial_points=10,
acq_func='EI',
random_state=42
)
print(f"Best: {result.x}, Score: {-result.fun:.4f}")
🔷 9. Визуализация результатов
from skopt.plots import plot_convergence, plot_objective
import matplotlib.pyplot as plt
# Convergence plot
plot_convergence(result)
plt.title('Convergence Plot')
plt.show()
# Objective function plot
plot_objective(result)
plt.tight_layout()
plt.show()
# История поиска
import pandas as pd
history = pd.DataFrame({
'iteration': range(len(result.func_vals)),
'score': -result.func_vals
})
history['best_so_far'] = history['score'].cummax()
plt.figure(figsize=(10, 6))
plt.plot(history['iteration'], history['score'], 'o', label='Score')
plt.plot(history['iteration'], history['best_so_far'], '-', label='Best so far')
plt.xlabel('Iteration')
plt.ylabel('Score')
plt.legend()
plt.title('Optimization Progress')
plt.show()
🔷 10. Преимущества и недостатки
✅ Преимущества
- Эффективнее Grid/Random Search
- Учитывает прошлые оценки
- Хорошо для дорогих функций
- Баланс exploration/exploitation
- Меньше итераций для хорошего результата
❌ Недостатки
- Медленнее на одну итерацию
- Плохо масштабируется (>20 параметров)
- Требует больше памяти
- GP плохо работает с категориями
- Не параллелизуется легко
🔷 11. Когда использовать
✅ Хорошо подходит
- Дорогие вычисления (обучение модели)
- Малый бюджет итераций (<200)
- Непрерывное пространство
- 2-20 параметров
- Нужен оптимум, не просто "хорошо"
❌ Плохо подходит
- Дешевые вычисления (используйте Grid Search)
- Много параметров (>20)
- Нужна параллелизация
- Дискретное пространство с большим числом значений
🔷 12. BO vs Grid vs Random Search
| Метод | Итераций | Эффективность | Когда использовать |
|---|
| Grid Search | Много | Низкая | Малое пространство, дешево |
| Random Search | Средне | Средняя | Baseline, высокие размерности |
| Bayesian Opt | Мало | Высокая | Дорого, малое число параметров |
🔷 13. Использование с XGBoost
import xgboost as xgb
from skopt import gp_minimize
from skopt.space import Real, Integer
def objective(params):
max_depth, learning_rate, n_estimators, subsample = params
model = xgb.XGBClassifier(
max_depth=max_depth,
learning_rate=learning_rate,
n_estimators=n_estimators,
subsample=subsample,
random_state=42,
use_label_encoder=False,
eval_metric='logloss'
)
model.fit(X_train, y_train)
score = model.score(X_val, y_val)
return -score
space = [
Integer(3, 10, name='max_depth'),
Real(0.01, 0.3, prior='log-uniform', name='learning_rate'),
Integer(50, 300, name='n_estimators'),
Real(0.5, 1.0, name='subsample')
]
result = gp_minimize(objective, space, n_calls=50)
print(f"Best params: {result.x}")
🔷 14. Параллелизация
# Optuna поддерживает параллелизацию
import optuna
from joblib import Parallel, delayed
def objective(trial):
lr = trial.suggest_loguniform('lr', 1e-6, 1e-1)
# ... обучение модели
return score
# Параллельная оптимизация
study = optuna.create_study(direction='maximize')
# n_jobs=-1 использует все ядра
study.optimize(objective, n_trials=100, n_jobs=-1)
# Или ручная параллелизация (для skopt)
from skopt import Optimizer
opt = Optimizer(space, base_estimator='GP', acq_func='EI')
# Запросить несколько точек параллельно
x = opt.ask(n_points=4)
# Оценить параллельно
y = Parallel(n_jobs=4)(delayed(objective)(xi) for xi in x)
# Обновить optimizer
opt.tell(x, y)
🔷 15. Практические советы
- Начните с малого: 50-100 итераций обычно достаточно
- Log-scale для LR: используйте prior='log-uniform'
- Мониторьте convergence: постройте график
- Сравните с Random: baseline для оценки пользы
- Warmstart: используйте прошлые результаты
- Optuna для продакшена: лучше интеграция и DB
🔷 16. Чек-лист
- [ ] Определить целевую функцию (objective)
- [ ] Задать пространство поиска
- [ ] Выбрать библиотеку (skopt/Optuna)
- [ ] Определить бюджет итераций
- [ ] Запустить оптимизацию
- [ ] Визуализировать convergence
- [ ] Оценить лучшие параметры на test
- [ ] Сравнить с Random Search
«Bayesian Optimization — золотой стандарт для подбора гиперпараметров когда вычисления дороги. Находит оптимум в 5-10 раз быстрее чем Random Search».